#include <SFML/Graphics/RenderTarget.hpp>
#include <SFML/Graphics/RectangleShape.hpp>
#include <RoundedRectangleShape.h>
#include <nice2d/application.h>
#include <nice2d/entity.h>
#include <nice2d/background.h>
#include <nice2d/bitmask.h>
#include <nice2d/message.h>

IMPLEMENT_OBJECT(Entity, Node)

#define BITMASK_MOUSE_IN        0
#define BITMASK_MOUSE_PRESSED   1
#define BITMASK_MOUSE_RELEASED  2
#define BITMASK_DIRTY_SIZE      3

class EntityData
{
public:
    EntityData(Entity &refEntity):
        entity(refEntity)
    {
        roundedRectangle.setCornerPointCount(36);
        roundedRectangle.setCornersRadius(0.0f);
    }

    Entity &entity;
    std::weak_ptr<Entity> parent;
    sf::RoundedRectangleShape roundedRectangle;
    uint32_t zValue = 0;
    BackgroundPointer background;
    Bitmask bitmask;
    std::optional<sf::Vector2i> oldPressPostion;
    std::optional<sf::Vector2f> center;

    void updatePositionByCenter()
    {
        if (center.has_value()) {
            auto size = entity.getSize();
            auto centerPostion = center.value();
            entity.setPosition(centerPostion.x - size.x * 0.5f, centerPostion.y - size.y * 0.5f);
            center.reset();
        }
    }
};

class EntityMouseListener : public MessageListener
{
public:
    EntityMouseListener(Entity &e):
        entity(e)
    {
    }

    bool onListener(std::shared_ptr<Message> message);
private:
    Entity &entity;
};

void Entity::adjustText(sf::Text &text, const sf::Vector2f &size, HMode h, float xmargin,
                        float ymargin)
{
    sf::FloatRect textRect = text.getGlobalBounds();
    float x = 0;
    float y = size.y * 0.5f - text.getCharacterSize() * 0.6f;

    switch (h) {
        case HMode_Left:
            x = xmargin;
            break;
        case HMode_Center:
            x = size.x * 0.5f - (textRect.width + textRect.left) * 0.5f;
            break;
        case HMode_Right:
            x = size.x - (int)(textRect.width + textRect.left) - xmargin;
            break;
        default:
            break;
    }

    text.setPosition(x, y);
}

sf::Vector2f Entity::adjustPosition(const sf::Vector2f &size, const sf::Vector2f &target, HMode h,
                                    VMode v, float xmargin, float ymargin)
{
    return adjustPosition(sf::FloatRect(0, 0, size.x, size.y), target, h, v, xmargin, ymargin);
}

sf::Vector2f Entity::adjustPosition(const sf::FloatRect &box, const sf::Vector2f &target, HMode h,
                                    VMode v, float xmargin, float ymargin)
{
    sf::Vector2f ret;
    if (h == HMode::HMode_Left)
        ret.x = box.left + xmargin;
    else if (h == HMode::HMode_Right)
        ret.x = box.left + box.width - target.x - xmargin;
    else
        ret.x = box.left + (box.width - target.x) * 0.5f;

    if (v == VMode::VMode_Top)
        ret.y = box.top + ymargin;
    else if (v == VMode::VMode_Bottom)
        ret.y = box.top + box.height - target.y - ymargin;
    else if (v == VMode::VMode_Center)
        ret.y = box.top + (box.height - target.y) * 0.5f;
    return ret;
}

Entity::Entity(const sf::Vector2f &size):
    data(new EntityData(*this))
{
    data->roundedRectangle.setSize(size);
    auto listener = std::make_shared<EntityMouseListener>(*this);
    registerMessageListener(listener);
}

Entity::~Entity()
{
}

void Entity::setSize(float width, float height)
{
    setSize(sf::Vector2f(width, height));
}

void Entity::setSize(const sf::Vector2f &size)
{
    if (data->roundedRectangle.getSize() == size)
        return;

    data->roundedRectangle.setSize(size);
    data->bitmask.setBit(BITMASK_DIRTY_SIZE, true);
}

sf::Vector2f Entity::getSize() const
{
    return data->roundedRectangle.getSize();
}

void Entity::setLeft(float left)
{
    auto position = getPosition();
    setPosition(left, position.y);
}

void Entity::setTop(float top)
{
    auto position = getPosition();
    setPosition(position.x, top);
}

void Entity::setRight(float right)
{
    auto position = getPosition();
    setPosition(right - getSize().x, position.y);
}

void Entity::setBottom(float bottom)
{
    auto position = getPosition();
    setPosition(position.x, bottom - getSize().y);
}

void Entity::setCenter(float x, float y)
{
    setCenter(sf::Vector2f(x, y));
}

void Entity::setCenter(const sf::Vector2f &center)
{
    data->center = center;
}

void Entity::setRounded(float radius, int pointcount)
{
    data->roundedRectangle.setCornerPointCount(pointcount);
    data->roundedRectangle.setCornersRadius(radius);
}

bool Entity::isUnderMouse()
{
    auto window = Application::getInstance()->getWindow();
    auto mousePosition = sf::Mouse::getPosition(*window);
    return getBoundingBox().contains(mousePosition.x, mousePosition.y);
}

bool Entity::isContainPoint(const sf::Vector2f &point)
{
    return getBoundingBox().contains(point);
}

sf::FloatRect Entity::getBoundingBox() const
{
    data->updatePositionByCenter();

    sf::FloatRect area = data->roundedRectangle.getLocalBounds();
    auto transform = getTransform();
    auto parent = getParent();
    while (!parent.expired()) {
        auto parentPointer = parent.lock();
        transform *= parentPointer->getTransform();
        parent = parentPointer->getParent();
    }
    return transform.transformRect(area);
}

void Entity::setBackgroundColor(const sf::Color &color)
{
    data->roundedRectangle.setFillColor(color);
    onBackgroundChanged();
}

void Entity::setBackground(BackgroundPointer background)
{
    if (background) {
        data->background = background;
        background->applyBackground(data->roundedRectangle);
        onBackgroundChanged();
    }
}

sf::Color Entity::getBackgroundColor() const
{
    return data->roundedRectangle.getFillColor();
}

void Entity::setZValue(uint32_t z)
{
    data->zValue = z;
}

uint32_t Entity::getZValue() const
{
    return data->zValue;
}

void Entity::setOutline(const sf::Color &color, float thickness)
{
    data->roundedRectangle.setOutlineColor(color);
    data->roundedRectangle.setOutlineThickness(thickness);
}

float Entity::getOutlineThickness() const
{
    return data->roundedRectangle.getOutlineThickness();
}

void Entity::setCornerRadius(float radius, uint32_t count)
{
    if(count > 0 && radius > 0.0f)
    {
        data->roundedRectangle.setCornerPointCount(count);
        data->roundedRectangle.setCornersRadius(radius);
    }
}

PTree Entity::serialize()
{
    PTree node;
    node.add_child(getObjectName(), Node::serialize());

    node.add_child("size", serializeObject(getSize()));
    node.add<int32_t>("zValue", getZValue());

    PTree round;
    round.add<float>("radius", data->roundedRectangle.getCornersRadius());
    round.add<int32_t>("count", data->roundedRectangle.getPointCount() / 4);
    node.add_child("round", round);

    PTree outline;
    outline.add<float>("thickness", data->roundedRectangle.getOutlineThickness());
    outline.add_child("color", serializeObject(data->roundedRectangle.getOutlineColor()));
    node.add_child("outline", outline);

    return node;
}

void Entity::deserialize(const PTree &node)
{
    Node::deserialize(node.get_child(getObjectName()));

    setSize(deserializeVector2f(node.get_child("size")));

    setZValue(node.get<int32_t>("zValue"));

    auto option = node.get_child_optional("round");
    if (option.has_value()) {
        auto current = option.value();
        setCornerRadius(current.get<float>("radius"), current.get<int32_t>("count"));
    }

    option = node.get_child_optional("outline");
    if (option.has_value()) {
        auto current = option.value();
        auto color = deserializeColor(current.get_child("color"));
        setOutline(color, current.get<float>("thickness"));
    }
}

void Entity::onUpdateNode(float deltaTime)
{
    data->updatePositionByCenter();

    (void)deltaTime;
    if (data->bitmask.contain(BITMASK_MOUSE_IN)) {
        auto mousePosition = sf::Mouse::getPosition(*Application::getInstance()->getWindow());
        auto contain = getBoundingBox().contains(mousePosition.x, mousePosition.y);
        if (!sf::Mouse::isButtonPressed(sf::Mouse::Left) && !contain) {
            data->bitmask.setBit(BITMASK_MOUSE_IN, false);
            onMouseExit();
        }
    }

    onUpdateEntity(deltaTime);
}

void Entity::onUpdateEntity(float deltaTime)
{
    (void)deltaTime;
    if (data->bitmask.contain(BITMASK_DIRTY_SIZE)) {
        data->bitmask.clearBit(BITMASK_DIRTY_SIZE);
        sizeChanged(getSize());
        onSizeChanged();
        onPositionChanged();
    }
}

void Entity::onDrawNode(sf::RenderTarget &target, sf::RenderStates states) const
{
    if (!isRenderObject())
        return;

    target.draw(data->roundedRectangle, states);
    onDrawEntity(target, states);
}

void Entity::onDrawEntity(sf::RenderTarget &target, sf::RenderStates states)const
{
}

void Entity::onMouseEnter()
{
}

void Entity::onMouseExit()
{
}

void Entity::onMousePressed(sf::Mouse::Button button)
{
    (void)button;
}

void Entity::onMouseReleased(sf::Mouse::Button button)
{
    (void)button;
}

void Entity::onMouseWheelScroll(float scroll)
{
    (void)scroll;
}

void Entity::onMouseMoved(int x, int y)
{
    (void)x, (void)y;
}

void Entity::onSizeChanged()
{
}

void Entity::onBackgroundChanged()
{
}

bool EntityMouseListener::onListener(std::shared_ptr<Message> message)
{
    if (message->getMessageType() != MessageType_SFML)
        return false;

    auto sfmlMessage = std::dynamic_pointer_cast<SFMLMessage>(message);

    if (entity.data->bitmask.contain(BITMASK_MOUSE_IN)) {
        if (sfmlMessage->getEvent().type == sf::Event::MouseButtonPressed) {
            entity.onMousePressed(sfmlMessage->getEvent().mouseButton.button);
        }

        if (sfmlMessage->getEvent().type == sf::Event::MouseButtonReleased) {
            entity.onMouseReleased(sfmlMessage->getEvent().mouseButton.button);
        }

        if (sfmlMessage->getEvent().type == sf::Event::MouseWheelScrolled) {
            entity.onMouseWheelScroll(sfmlMessage->getEvent().mouseWheelScroll.delta);
        }

        if (sfmlMessage->getEvent().type == sf::Event::MouseMoved) {
            auto position = sf::Mouse::getPosition(*Application::getInstance()->getWindow());
            entity.onMouseMoved(position.x, position.y);
        }
    }

    auto contain = entity.isUnderMouse();
    if (contain) {
        if (!entity.data->bitmask.contain(BITMASK_MOUSE_IN)) {
            entity.data->bitmask.setBit(BITMASK_MOUSE_IN, true);
            entity.onMouseEnter();
        }
    } else {
        if (entity.data->bitmask.contain(BITMASK_MOUSE_IN)) {
            if (!sf::Mouse::isButtonPressed(sf::Mouse::Left)) {
                entity.data->bitmask.setBit(BITMASK_MOUSE_IN, false);
                entity.onMouseExit();
            }
        }
    }
    return false;
}
